/**
   Copyright 2010 Carlo Alberto Degli Atti (lord kada)

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package it.concept.pin;

import it.concept.pin.annotations.Pin;
import it.concept.pin.annotations.Transport;
import it.concept.pin.exceptions.DataModelFieldDoesNotExist;
import it.concept.pin.exceptions.DataModelMethodDoesNotExist;
import it.concept.pin.exceptions.PinException;
import it.concept.pin.exceptions.SetterMethodIsNotVoid;
import it.concept.pin.exceptions.WrongNumberOfParametersInSetterMethod;
import it.concept.pin.proxy.ProxyWrapper;

import java.lang.annotation.AnnotationFormatError;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class PinManager {

	private static final String GETTER_METHOD_PREFIX = "get";
	
	private static final String SETTER_METHOD_PREFIX = "set";

	public static Object toPojo( Class<?> pojoClass, Object dataModel ) throws PinException {
		
		Object pojo = null;
		
		if ( dataModel != null ) {
				
			try {
				
				pojo = pojoClass.newInstance();
				
				if ( pojoClass.getAnnotation( Pin.class ) == null )
					
					throw new AnnotationFormatError( pojoClass.getName() + " is not @Pin annotated" );
				
				// Let's process the class members annotated with @Transport
				for ( Field field : pojoClass.getDeclaredFields() ) {
					
					if ( field.getAnnotation( Transport.class ) != null ) {
						
						field.setAccessible( true );
						
						Field instrumentalDeclaredField = dataModel.getClass().getDeclaredField( field.getName() );
					
						if ( instrumentalDeclaredField != null ) {

							instrumentalDeclaredField.setAccessible( true );
							
							if ( field.getType().getAnnotation( Pin.class ) != null ) {
								
								Object subPojo = toPojo( field.getType(), instrumentalDeclaredField.get( dataModel ) );
								
								field.set( pojo, subPojo );
								
							}
							
							else
							
								field.set( pojo, instrumentalDeclaredField.get( dataModel ) );

						}
						
						else 
							
							throw new DataModelFieldDoesNotExist( dataModel.getClass().getName(), field.getName() );
						
					}
					
				}
				
				// Let's process the setter methods that are annotated with @Transport
				// Note that a setter method must: a) return void b) expect one parameter
				for ( Method method : pojoClass.getMethods() ) {
				
					if ( method.getAnnotation( Transport.class ) != null ) {
						
						if (  method.getReturnType().equals( void.class ) ) {
						
							method.setAccessible( true );
						
							Class<?>[] parameterTypes = method.getParameterTypes();
						
							if ( parameterTypes.length != 1 )
								
								throw new WrongNumberOfParametersInSetterMethod( method.getName() );
							
							String dataModelGetterMethodName = method.getName().replaceAll( SETTER_METHOD_PREFIX, GETTER_METHOD_PREFIX );
							
							Method instrumentalGetterMethod = dataModel.getClass().getMethod( dataModelGetterMethodName, new Class<?>[]{} );
	
							if ( instrumentalGetterMethod != null ) {
								
								Object resultInstrumentalGetterMethod = instrumentalGetterMethod.invoke( dataModel, new Object[] { } );
								
								if ( parameterTypes[0].getAnnotation( Pin.class ) != null ) {
								
									Object subPojo = toPojo( parameterTypes[0], resultInstrumentalGetterMethod );
										
									method.invoke( pojo, subPojo );
									
								}
								
								else
									
									method.invoke( pojo, new Object[] { resultInstrumentalGetterMethod } );

							}
							
							else
								
								throw new DataModelMethodDoesNotExist( dataModel.getClass().getName(), dataModelGetterMethodName );
								
						}
						
						else
							
							throw new SetterMethodIsNotVoid( method.getName() );
						
					}
					
				}
	
			}
			catch ( Exception e ) {
				
				throw new PinException( e );
				
			}
		
		}
			
		return pojo;
		
	}

	public static Object toInterface( Object pojo ) throws PinException {

		try {
			
			Class<?> pojoClass = pojo.getClass();
			
			Pin pinAnnotation = pojoClass.getAnnotation( Pin.class );
			
			if ( pinAnnotation == null )
				
				throw new AnnotationFormatError( pojoClass.getName() + " is not @Pin annotated" );
			
			return ProxyWrapper.createProxy( Class.forName( pinAnnotation.javaInterfaceName() ), pojo );
			
		} catch ( Exception e) {
			
			throw new PinException( e );
			
		}
		
	}
	
}